﻿using System;
using System.Text.RegularExpressions;
using NetBay.Core.BusinessEntities;
using System.Collections.Generic;
using Rio.Framework.Pages.Context;
using System.Linq;
using System.Xml.Linq;
using Rio.Framework.Pages.AppCode.Models;
using Rio.Applications.Web.Services;
using Rio.Framework.Pages.Helpers.Utils;

namespace Rio.Framework.Pages.Manager
{
    public class ZipFoldersManager
    {
        public delegate void GetZipFinishedEventHandler(object sender, byte[] zip, int totalFolders, int totalFoldersProcessed);

        public event EventHandler InitializationFinished;
        public event EventHandler ProcessFinished;
        public event GetZipFinishedEventHandler GetZipFinished;
        private Dictionary<Folder, DataItemTreeViewCollection> _itemsFolders;
        private Dictionary<string, string> _documentsAndItems;
        private Guid _operationGuid;
        private int _totalFoldersProcessed;

        public long OrganizationId { get; set; }
        public FolderType FolderType { get; set; }
        public List<Folder> Folders { get; set; }

        /// <summary>
        /// Prevents a default instance of the <see cref="ZipFoldersManager"/> class from being created.
        /// </summary>
        /// <param name="organizationId">The organization id.</param>
        /// <param name="folderType">Type of the folder.</param>
        /// <param name="foldersToZip">The folders to zip.</param>
        public ZipFoldersManager(long organizationId, FolderType folderType, List<Folder> foldersToZip)
        {
            if (folderType == null)
                throw new ArgumentNullException("folderType");

            if (foldersToZip == null)
                throw new ArgumentNullException("foldersToZip");

            if (foldersToZip.Count == 0)
                throw new ArgumentException("Parameter cannot be empty.", "foldersToZip");

            OrganizationId = organizationId;
            FolderType = folderType;
            Folders = foldersToZip;
        }

        /// <summary>
        /// Initializations this instance.
        /// </summary>
        public void Initialization()
        {
            BuildItemsAndDocumentsStructureRecursive();
        }

        /// <summary>
        /// Processes this instance.
        /// </summary>
        public void Process()
        {
            _operationGuid = Guid.NewGuid();
            _totalFoldersProcessed = 0;

            ZipFoldersRecursive();
        }

        /// <summary>
        /// Gets the zip.
        /// </summary>
        public void GetZip()
        {
            WCFService.RioWcfServices.WCFServiceClient wcfService = WCFService.ServiceManager.GetRioWCFServiceClient();
            wcfService.GetExtractedFoldersCompleted += wcfService_GetExtractedFoldersCompleted;
            wcfService.GetExtractedFoldersAsync(
                _operationGuid,
                WCFService.ServiceManager.GetLicenseKey());
        }

        /// <summary>
        /// Clears the server.
        /// </summary>
        public void ClearServer()
        {
            WCFService.RioWcfServices.WCFServiceClient wcfService = WCFService.ServiceManager.GetRioWCFServiceClient();
            wcfService.DeleteExtractFolderAsync(
                _operationGuid,
                WCFService.ServiceManager.GetLicenseKey());
        }

        /// <summary>
        /// Handles the GetExtractedFoldersCompleted event of the wcfService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Rio.Framework.WCFService.RioWcfServices.GetExtractedFoldersCompletedEventArgs"/> instance containing the event data.</param>
        private void wcfService_GetExtractedFoldersCompleted(object sender, WCFService.RioWcfServices.GetExtractedFoldersCompletedEventArgs e)
        {
            WCFService.RioWcfServices.WCFServiceClient wcfService = sender as WCFService.RioWcfServices.WCFServiceClient;

            if (wcfService != null)
                wcfService.GetExtractedFoldersCompleted -= wcfService_GetExtractedFoldersCompleted;

            OnGetZipFinished(e.Result.Value, Folders.Count, _totalFoldersProcessed);
        }

        /// <summary>
        /// Zips the folders recursive.
        /// </summary>
        /// <param name="folderIndex">Index of the folder.</param>
        /// <param name="foldersFailed">The folders failed.</param>
        private void ZipFoldersRecursive(int folderIndex = 0, int foldersFailed = 0)
        {
            Folder currentFolder = _itemsFolders.ElementAt(folderIndex).Key;
            DataItemTreeViewCollection currentItems = _itemsFolders[currentFolder];
            List<Document> currentDocuments = currentItems[0].ListDocument;

            /*
             * Build Items and Documents
             */

            _documentsAndItems = new Dictionary<string, string>();

            foreach (Document document in currentDocuments)
            {
                BuildDocumentsAndItemsRecursive(document, currentItems, String.Empty);
            }

            /*
             * Build Index and Values
             */

            Dictionary<string, string> folderIndexValues = new Dictionary<string, string>();

            for (int i = 0; i < currentFolder.ListIndex.Count; i++)
            {
                folderIndexValues.Add(currentFolder.ListIndex.ElementAt(i).IndexLabel, currentFolder.ListValues.ElementAt(i));
            }

            string folderName = Utils.GetFolderLabel(currentFolder);

            WCFService.RioWcfServices.WCFServiceClient wcfService = WCFService.ServiceManager.GetRioWCFServiceClient();
            wcfService.ExtractFolderCompleted += wcfService_ExtractFolderCompleted;
            wcfService.ExtractFolderAsync(
                _operationGuid,
                currentFolder.Identifier,
                String.IsNullOrWhiteSpace(folderName) ? currentFolder.Identifier.ToString() : folderName,
                folderIndexValues,
                _documentsAndItems,
                WCFService.ServiceManager.GetLicenseKey(),
                new Tuple<int, int>(folderIndex, foldersFailed));
        }

        /// <summary>
        /// Handles the ExtractFolderCompleted event of the wcfService control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Rio.Framework.WCFService.RioWcfServices.ExtractFolderCompletedEventArgs"/> instance containing the event data.</param>
        private void wcfService_ExtractFolderCompleted(object sender, WCFService.RioWcfServices.ExtractFolderCompletedEventArgs e)
        {
            WCFService.RioWcfServices.WCFServiceClient wcfService = sender as WCFService.RioWcfServices.WCFServiceClient;

            if (wcfService != null)
                wcfService.ExtractFolderCompleted -= wcfService_ExtractFolderCompleted;

            Tuple<int, int> folderInformations = ((Tuple<int, int>)e.UserState);
            int folderIndex = folderInformations.Item1;
            int foldersFailed = folderInformations.Item2;

            if (!e.Result.Value)
            {
                //Increment number of folders failed to display in popup
                foldersFailed++;
            }

            folderIndex++;
            if (folderIndex == _itemsFolders.Count)
            {
                //We done all folders
                _totalFoldersProcessed = folderIndex;
                OnProcessFinished();
            }
            else
            {
                //Recall on next folder
                ZipFoldersRecursive(folderIndex, foldersFailed);
            }
        }

        /// <summary>
        /// Builds the documents and items recursivly.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="itemsCollection">The items collection.</param>
        /// <param name="parentItemPath">The parent item path.</param>
        private void BuildDocumentsAndItemsRecursive(Document document, DataItemTreeViewCollection itemsCollection, string parentItemPath)
        {
            foreach (var item in itemsCollection)
            {
                //Build the path with items, ex = "Item1/Item2/Item3..."
                string currentItemPath = System.IO.Path.Combine(parentItemPath, item.Text);

                if (document.ItemRank == item.Code)
                {
                    string finalFilename = document.OriginalNameFile;
                    string fullPath = System.IO.Path.Combine(GlobalWebContext.ParameterStorage, document.RelativePath, document.ImageName);

                    //Count if there is a dcument with the same name
                    int occurences = (from documentsAndItem in _documentsAndItems
                                      let sameNameRegex = new Regex(
                                                    String.Format(@"[^#]*#{0}(\(\d+\))?\{1}",
                                                    System.IO.Path.GetFileNameWithoutExtension(finalFilename),
                                                    System.IO.Path.GetExtension(finalFilename)))
                                      select sameNameRegex.Match(documentsAndItem.Key)).Count(match => match.Success);

                    //if several documents with same name exists, we rename the document
                    if (occurences > 0)
                        finalFilename = String.Format("{0}({1}){2}",
                                                      System.IO.Path.GetFileNameWithoutExtension(finalFilename),
                                                      occurences,
                                                      System.IO.Path.GetExtension(finalFilename));

                    //If the document is in the item, we add the fullpath on stock + original name and its item path
                    _documentsAndItems.Add(String.Format("{0}#{1}", fullPath, finalFilename), currentItemPath);
                }
                else
                {
                    if (item.Items.Count > 0)
                    {
                        //If the current item have sub items, we recall this method with sub items
                        BuildDocumentsAndItemsRecursive(document, item.Items, currentItemPath);
                    }
                }
            }
        }

        /// <summary>
        /// Builds the items and documents structure.
        /// </summary>
        private void BuildItemsAndDocumentsStructureRecursive(int itemsFolderIndex = 0)
        {
            if (_itemsFolders == null)
                _itemsFolders = new Dictionary<Folder, DataItemTreeViewCollection>();

            Folder folder = Folders[itemsFolderIndex];

            //Create an entry for currenyt folder and items structure
            DataItemTreeViewCollection itemsStructure = BuildItemsStructure(XDocument.Parse(FolderType.TreeNodeForItem).Element("Root"));

            /*
             * Create Unclassified item
             */


            DataItemTreeView unclassifiedItem = new DataItemTreeView
                                                    {
                                                        Code = "000000000000",
                                                        Text = String.Empty,
                                                    };

            itemsStructure.Insert(0, unclassifiedItem);

            _itemsFolders.Add(
                folder,
                itemsStructure);

            //get all documents for this folder
            BusinessEntitiesDomainContext businessEntitiesDomainContext = GlobalWebContext.BusinessEntitiesDomainContext;
            var query = businessEntitiesDomainContext.GetDocumentsQuery(FolderType.Identifier, folder.Identifier);
            businessEntitiesDomainContext.Load(
                query,
                result =>
                {
                    int itemsFolderIndexSource = ((int)result.UserState);

                    if (!result.HasError)
                    {
                        //Save list of documents always in the first node
                        _itemsFolders.ElementAt(itemsFolderIndexSource).Value[0].ListDocument = result.Entities.ToList();
                    }

                    //Increment to the next folder
                    itemsFolderIndexSource++;
                    if (itemsFolderIndexSource == Folders.Count)
                    {
                        //We done all folders
                        OnInitializationFinished();
                    }
                    else
                    {
                        //recall for the next folder
                        BuildItemsAndDocumentsStructureRecursive(itemsFolderIndexSource);
                    }
                },
                itemsFolderIndex);
        }

        /// <summary>
        /// Builds the items structure.
        /// </summary>
        /// <param name="xElement">The x element.</param>
        /// <returns></returns>
        private DataItemTreeViewCollection BuildItemsStructure(XElement xElement)
        {
            DataItemTreeViewCollection itemsCollection = new DataItemTreeViewCollection(null);

            /*
             * Create real items recursively
             */

            foreach (var xItem in xElement.Elements("TreeViewItem"))
            {
                DataItemTreeView itemTree = new DataItemTreeView
                                                {
                                                    Code = xItem.Attribute("Code").Value,
                                                    Text = xItem.Attribute("Header").Value,
                                                    Items = BuildItemsStructure(xItem)
                                                };

                itemsCollection.Add(itemTree);
            }

            return itemsCollection;
        }

        /// <summary>
        /// Called when [process finished].
        /// </summary>
        protected void OnProcessFinished()
        {
            if (ProcessFinished != null)
            {
                ProcessFinished(this, new EventArgs());
            }
        }

        /// <summary>
        /// Called when [initialization finished].
        /// </summary>
        protected void OnInitializationFinished()
        {
            if (InitializationFinished != null)
            {
                InitializationFinished(this, new EventArgs());
            }
        }

        /// <summary>
        /// Called when [get zip finished].
        /// </summary>
        protected void OnGetZipFinished(byte[] zip, int totalFolders, int totalFoldersProcessed)
        {
            if (GetZipFinished != null)
            {
                GetZipFinished(this, zip, totalFolders, totalFoldersProcessed);
            }
        }
    }
}